import time

from django.db.models import Sum, Q
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver

from oa.models import *
from tb3.utils import num_value, plato2sg, sg2plato


def set_instance_code(instance, obj, date_field='order_date'):
    date_v = getattr(instance, date_field)
    date_str = str(date_v.strftime('%Y%m%d'))
    order_date = datetime.datetime.strptime(str(date_v), '%Y-%m-%d')
    date_field_dict = {date_field: order_date}
    if instance.code:
        if date_str not in str(instance.code):
            instance.code = None
        if '-' not in str(instance.code):
            instance.code = None
    if not instance.code:
        pre_records = obj.objects.filter(Q(**date_field_dict) | Q(code__contains=date_str))
        if instance.pk:
            pre_records = pre_records.exclude(pk=instance.pk)
        pre_records = pre_records.values('code')
        codes = list()
        for pre_record in pre_records:
            codes.append(pre_record['code'])
        code_count = 1
        while True:
            code = '{}-{}'.format(
                    date_str,
                    '{}'.format(code_count).zfill(2)
                )
            if code in codes:
                code_count += 1
            else:
                break
        instance.code = code
    else:
        code = getattr(instance, 'code')
        code_count = int(code.split('-')[-1])
    return code_count


def set_operator(instance, field_name='operator'):
    if not getattr(instance, field_name) and instance.created_by:
        staff, created = Staff.objects.get_or_create(related_user=instance.created_by)
        setattr(instance, field_name, staff)


def set_linked_field(instance, primary_field='report', linked_field='report_is_upload'):
    if getattr(instance, primary_field):
        setattr(instance, linked_field, True)
    else:
        setattr(instance, linked_field, False)


@receiver(pre_save, sender=Staff)
def pre_save_staff(sender, instance, **kwargs):
    if not instance.full_name:
        if instance.last_name and instance.first_name:
            instance.full_name = '{}{}'.format(instance.last_name, instance.first_name)


@receiver(pre_save, sender=MaterialBatch)
def pre_save_material_batch(sender, instance, **kwargs):
    if instance.shelf_life:
        if instance.production_date and not instance.expiration_date:
            instance.expiration_date = \
                instance.production_date + datetime.timedelta(days=int(getattr(instance, 'shelf_life')))
        if not instance.production_date and instance.expiration_date:
            instance.production_date = \
                instance.expiration_date - datetime.timedelta(days=int(getattr(instance, 'shelf_life')))
    if instance.package_unit:
        if not instance.unit:
            instance.unit = getattr(instance, 'package_unit').unit
        else:
            if not instance.unit == getattr(instance, 'package_unit').unit:
                instance.unit = getattr(instance, 'package_unit').unit
    set_linked_field(instance, primary_field='report', linked_field='report_is_upload')


@receiver(pre_save, sender=MaterialPurchaseOrder)
def pre_save_material_purchase_order(sender, instance, **kwargs):
    # 计算 code
    set_instance_code(instance, sender, date_field='order_date')
    # 计算 total_cost
    if instance.total_price:
        instance.total_cost = instance.total_price
        if instance.carriage_cost:
            instance.total_cost = float(instance.total_cost) + float(instance.carriage_cost)
        if instance.total_discount:
            instance.total_cost = float(instance.total_cost) - float(instance.total_discount)
    # 自动获取 applied_by
    set_operator(instance, field_name='applied_by')


@receiver(post_save, sender=MaterialPurchaseOrder)
def post_save_material_purchase_order(sender, instance, **kwargs):
    # 更新 MaterialPurchaseOrderItem 的 unit_cost
    delta_cost = 0
    if instance.carriage_cost:
        delta_cost += float(instance.carriage_cost)
    if instance.total_discount:
        delta_cost -= float(instance.total_discount)
    items_amount = MaterialPurchaseOrderItem.objects.filter(
        related_purchase_order_id=instance.pk
    ).aggregate(sum=Sum('amount'))
    total_kg = items_amount['sum']
    if total_kg:
        unit_delta_cost = delta_cost / float(total_kg)
        items = MaterialPurchaseOrderItem.objects.filter(related_purchase_order_id=instance.pk)
        for item in items:
            if item.unit_price:
                unit_cost = float(item.unit_price) + unit_delta_cost
            else:
                item.unit_price = 0
                unit_cost = unit_delta_cost
            if not item.unit_cost == unit_cost:
                item.unit_cost = unit_cost
                item.save(update_fields=['unit_cost'])


@receiver(pre_save, sender=MaterialPurchaseOrderItem)
def pre_save_material_purchase_order_item(sender, instance, **kwargs):
    # 计算 amount、price 和 exp_record_date
    if instance.packs and instance.package_unit:
        instance.amount = float(instance.packs) * float(getattr(instance, 'package_unit').amount)
        if instance.price and instance.amount:
            instance.unit_price = float(instance.price) / float(instance.amount)
        else:
            instance.unit_price = 0
    if not instance.exp_record_date and instance.related_purchase_order:
        instance.exp_record_date = getattr(instance, 'related_purchase_order').exp_record_date


@receiver(post_save, sender=MaterialPurchaseOrderItem)
def post_save_material_purchase_order_item(sender, instance, **kwargs):
    if instance.related_purchase_order:
        need_update = False
        state = '1'
        ois = MaterialPurchaseOrderItem.objects.filter(
            related_purchase_order=instance.related_purchase_order
        ).aggregate(total_price=Sum('price'))
        # 总价更新
        total_price = ois['total_price']
        related_purchase_order = MaterialPurchaseOrder.objects.get(pk=instance.related_purchase_order.pk)
        if total_price:
            total_price = round(float(total_price), 2)
            if not related_purchase_order.total_price:
                related_purchase_order.total_price = total_price
                need_update = True
            else:
                if round(float(related_purchase_order.total_price), 2) - total_price != 0:
                    related_purchase_order.total_price = total_price
                    need_update = True
        # 是否已经都入库
        it_c_all = MaterialPurchaseOrderItem.objects.filter(
            related_purchase_order=instance.related_purchase_order
        ).count()
        it_c_arr = MaterialPurchaseOrderItem.objects.filter(
            related_purchase_order=instance.related_purchase_order
        ).filter(
            is_arrived=True
        ).count()
        # 获取入库状态
        if it_c_all:
            if it_c_all == it_c_arr:
                state = '3'
            else:
                state = '2'
        # 判断是否需要更新
        if not related_purchase_order.state == state:
            need_update = True
            related_purchase_order.state = state
        # 如果需要更新，则进行更新
        if need_update:
            related_purchase_order.save(update_fields=['total_price', 'state'])


@receiver(pre_save, sender=MaterialInOrder)
def pre_save_material_in_order(sender, instance, **kwargs):
    # 计算 code
    code_count = set_instance_code(instance, sender, date_field='record_date')
    # 计算 record_datetime
    instance.record_datetime = '{} {}'.format(
        instance.record_date.strftime('%Y-%m-%d'),
        time.strftime('%H:%M:%S', time.localtime(code_count))
    )
    # 自动获取 operator
    set_operator(instance)


@receiver(pre_save, sender=MaterialIn)
def pre_save_material_in(sender, instance, **kwargs):
    # update unit, amount
    if instance.packs and instance.material_batch:
        package_unit = getattr(instance, 'material_batch').package_unit
        if package_unit:
            per_package_amount = package_unit.amount
            instance.unit = package_unit.unit
            if per_package_amount and instance.packs:
                instance.amount = float(per_package_amount) * float(getattr(instance, 'packs'))
    # 自动获取 material 的关联
    if instance.material_batch:
        instance.material = instance.material_batch.material
    # 获取关联采购明细
    if instance.related_order:
        related_purchase_order = getattr(instance.related_order, 'related_purchase_order')
        if related_purchase_order:
            related_purchase_detail = MaterialPurchaseOrderItem.objects.filter(
                related_purchase_order=related_purchase_order,
                material=instance.material
            )
            if related_purchase_detail.count() >= 1:
                related_purchase_detail = related_purchase_detail.first()
                # 进行关联
                instance.related_purchase_detail = related_purchase_detail
                # 获取 unit_price, unit_cost
                instance.unit_price = related_purchase_detail.unit_price
                instance.unit_cost = related_purchase_detail.unit_cost


def update_material_batch(storage_info, material_batch_id):
    k_list = ['total_in', 'left_amount', 'unit_price', 'unit_cost']
    material_batch_data = {}
    for k in k_list:
        material_batch_data[k] = storage_info[k]
    MaterialBatch.objects.filter(pk=material_batch_id).update(**material_batch_data)


def update_next_record(material_batch_id, current_record_datetime):
    # 后续出入库记录联动更新
    next_mi_record = MaterialIn.objects.filter(
        material_batch_id=material_batch_id,
        related_order__record_datetime__gt=current_record_datetime,
    ).order_by(
        'related_order__record_datetime',
    ).first()
    next_mo_record = MaterialOut.objects.filter(
        material_batch_id=material_batch_id,
        related_order__record_datetime__gt=current_record_datetime,
    ).order_by(
        'related_order__record_datetime',
    ).first()
    if next_mi_record and next_mo_record:
        if not next_mi_record.related_order:
            next_mi_record.delete()
        elif not next_mo_record.related_order:
            next_mo_record.delete()
        else:
            if next_mi_record.related_order.record_datetime < next_mo_record.related_order.record_datetime:
                next_mi_record.save()
            else:
                next_mo_record.save()
    else:
        if next_mi_record:
            next_mi_record.save()
        if next_mo_record:
            next_mo_record.save()


@receiver(post_save, sender=MaterialIn)
def post_save_material_in(sender, instance, **kwargs):
    # 原料相同采购订单的数量统计
    total_amount_in = MaterialIn.objects.filter(
        material_id=instance.material_id
    ).filter(
        related_order__related_purchase_order_id=instance.related_order.related_purchase_order_id
    ).aggregate(
        total_amount=Sum('amount')
    )
    total_amount = total_amount_in['total_amount']
    # 计算库存
    current_amount = num_value(instance.amount, return_zero=True)
    current_unit_price = num_value(instance.unit_price, return_zero=True)
    current_unit_cost = num_value(instance.unit_cost, return_zero=True)
    current_record_datetime = instance.related_order.record_datetime
    storage_info = {
        'material_batch_id': instance.material_batch_id,
        'warehouse_id': instance.warehouse_id,
        'cw_total_in': 0,
        'cw_left_amount': 0,
        'total_in': 0,
        'left_amount': 0,
        'unit_price': 0,
        'unit_cost': 0,
        'record_datetime': current_record_datetime,
    }
    # 获取该原料批次上次记录的库存
    pre_storage_cw = MaterialBatchStorage.objects.filter(
        material_batch_id=instance.material_batch_id,
        warehouse_id=instance.warehouse_id,
        record_datetime__lt=current_record_datetime,
    ).order_by('-record_datetime').first()
    if pre_storage_cw:
        pre_cw_total_in = float(pre_storage_cw.cw_total_in)
        pre_cw_left_amount = float(pre_storage_cw.cw_left_amount)
        # 计算当前仓库剩余
        storage_info['cw_total_in'] = pre_cw_total_in + current_amount
        storage_info['cw_left_amount'] = pre_cw_left_amount + current_amount
    else:
        storage_info['cw_total_in'] = current_amount
        storage_info['cw_left_amount'] = current_amount
    pre_storage = MaterialBatchStorage.objects.filter(
        material_batch_id=instance.material_batch_id,
        record_datetime__lt=current_record_datetime
    ).order_by('-record_datetime').first()
    if pre_storage:
        pre_total_in = float(pre_storage.total_in)
        pre_left_amount = float(pre_storage.left_amount)
        pre_unit_price = float(pre_storage.unit_price)
        pre_unit_cost = float(pre_storage.unit_cost)
        # 计算当前剩余
        total_in = pre_total_in + current_amount
        storage_info['total_in'] = total_in
        storage_info['left_amount'] = pre_left_amount + current_amount
        storage_info['unit_price'] = (pre_unit_price * pre_total_in + current_amount * current_unit_price) / total_in
        storage_info['unit_cost'] = (pre_unit_cost * pre_total_in + current_amount * current_unit_cost) / total_in
    else:
        storage_info['total_in'] = current_amount
        storage_info['left_amount'] = current_amount
        storage_info['unit_price'] = current_unit_price
        storage_info['unit_cost'] = current_unit_cost
    MaterialBatchStorage.objects.update_or_create(
        material_in_id=instance.pk,
        defaults=storage_info
    )
    update_material_batch(storage_info, instance.material_batch_id)
    # 更新入库情况
    update_next_record(instance.material_batch_id, current_record_datetime)
    # 对采购明细进行更新
    if instance.related_order:
        is_arrived = False
        if instance.related_purchase_detail:
            amount = instance.related_purchase_detail.amount
            if amount:
                if total_amount >= float(amount)*0.98:
                    is_arrived = True
            MaterialPurchaseOrderItem.objects.filter(
                pk=instance.related_purchase_detail.pk
            ).update(
                amount_in=total_amount,
                is_arrived=is_arrived
            )


@receiver(post_save, sender=ProductBatch)
def post_save_product_batch(sender, instance, **kwargs):
    if instance.current_tank:
        datetime_start = '{} {}'.format(
                instance.start_date.strftime('%Y-%m-%d'),
                time.strftime('%H:%M:%S', time.localtime(int(instance.pk)))
            )
        # 插入发酵罐使用记录
        EquipmentUsageRecord.objects.update_or_create(
            equipment_id=instance.current_tank_id,
            product_batch_id=instance.pk,
            defaults={'datetime_start': datetime_start})
        # 判断当前记录的时间是不是最后的
        pre_usage_count = EquipmentCurrentProductBatch.objects.filter(
            equipment_id=instance.current_tank_id
        ).filter(
            datetime_start__gt=datetime_start
        ).count()
        # 如果之前没有记录
        if pre_usage_count == 0:
            EquipmentCurrentProductBatch.objects.update_or_create(
                equipment_id=instance.current_tank_id,
                defaults={
                    'product_batch_id': instance.pk,
                    'datetime_start': datetime_start,
                    'tank_state': instance.product_state
                },
            )


@receiver(pre_save, sender=BrewBatch)
def pre_save_brew_batch(sender, instance, **kwargs):
    # 自动获取 operator
    set_operator(instance)
    set_linked_field(instance, primary_field='report', linked_field='file_is_upload')


@receiver(pre_save, sender=PackBatch)
def pre_save_pack_batch(sender, instance, **kwargs):
    # 获取 product_package
    if instance.product_batch:
        if not instance.product_name:
            instance.product_name = getattr(instance, 'product_batch').product_name
        if not instance.product_package and instance.package_unit:
            product_package, created = ProductPackage.objects.get_or_create(
                product_name=instance.product_name,
                package_unit=instance.package_unit
            )
            instance.product_package = product_package
    # 自动更新 is_checked
    set_linked_field(instance, primary_field='report', linked_field='is_checked')
    set_linked_field(instance, primary_field='record_file', linked_field='file_is_upload')
    # 自动获取 operator
    set_operator(instance)


@receiver(post_save, sender=PackBatch)
def post_save_pack_batch(sender, instance, **kwargs):
    if instance.last_batch:
        finish_date = None
        if instance.pack_end:
            finish_date = '{}'.format(instance.pack_end.strftime('%Y-%m-%d'))
        else:
            if instance.pack_start:
                finish_date = '{}'.format(instance.pack_start.strftime('%Y-%m-%d'))
        if finish_date:
            product_batch = instance.product_batch
            product_batch.finish_date = finish_date
            product_batch.save()


@receiver(pre_save, sender=ProcessRecord)
def pre_save_process_record(sender, instance, **kwargs):
    # 自动获取 operator
    set_operator(instance)
    set_linked_field(instance, primary_field='file', linked_field='file_is_upload')


@receiver(post_save, sender=ProcessRecord)
def post_save_process_record(sender, instance, **kwargs):
    if instance.current_tank and instance.product_batch:
        ProductBatch.objects.filter(pk=instance.product_batch.pk).update(current_tank=instance.current_tank)
    if instance.process_start:
        tank_state = process_type_related_state.get(instance.process_type)
        print('tank_state', tank_state)
        if instance.current_tank:
            EquipmentUsageRecord.objects.update_or_create(
                equipment_id=instance.current_tank_id,
                product_batch_id=instance.product_batch_id,
                defaults={
                    'datetime_start': instance.process_start,
                    'process_record_id': instance.pk
                })
            pre_usage_count = EquipmentCurrentProductBatch.objects.filter(
                equipment_id=instance.current_tank_id
            ).filter(
                datetime_start__gt=instance.process_start
            ).count()
            if pre_usage_count == 0:
                EquipmentCurrentProductBatch.objects.update_or_create(
                    equipment_id=instance.current_tank_id,
                    defaults={
                        'product_batch_id': instance.product_batch_id,
                        'datetime_start': instance.process_start,
                        'tank_state': tank_state
                    },
                )
        else:
            EquipmentCurrentProductBatch.objects.filter(
                equipment_id=instance.product_batch.current_tank_id,
                product_batch_id=instance.product_batch_id,
            ).update(tank_state=tank_state)


@receiver(pre_save, sender=MaterialOutOrder)
def pre_save_material_out_order(sender, instance, **kwargs):
    # 计算 code
    code_count = set_instance_code(instance, sender, date_field='record_date')
    # 计算 record_datetime
    instance.record_datetime = '{} {}'.format(
        instance.record_date.strftime('%Y-%m-%d'), time.strftime('%H:%M:%S', time.localtime(code_count+3600))
    )
    # 获取 product_batch
    if instance.brew_batch or instance.pack_batch or instance.process_record:
        if instance.pack_batch:
            instance.product_batch = getattr(instance, 'pack_batch').product_batch
            instance.brew_batch = None
            instance.process_record = None
            instance.group_batch = None
        elif instance.process_record:
            instance.product_batch = getattr(instance, 'process_record').product_batch
            instance.brew_batch = None
            instance.pack_batch = None
            instance.group_batch = None
        elif instance.group_batch:
            instance.product_batch = getattr(instance, 'group_batch').product_batch
            instance.brew_batch = None
            instance.pack_batch = None
            instance.process_record = None
        else:
            instance.product_batch = getattr(instance, 'brew_batch').product_batch
    else:
        instance.product_batch = None
    # 自动获取 operator
    set_operator(instance)


@receiver(post_save, sender=MaterialOutOrder)
def post_save_material_out_order(sender, instance, **kwargs):
    mos = MaterialOut.objects.filter(related_order_id=instance.pk)
    for m_o in mos:
        m_o.save()


@receiver(pre_save, sender=MaterialOut)
def pre_save_material_out(sender, instance, **kwargs):
    if instance.related_order:
        instance.product_batch = getattr(instance, 'related_order').product_batch
        instance.brew_batch = getattr(instance, 'related_order').brew_batch
        instance.pack_batch = getattr(instance, 'related_order').pack_batch
    if instance.material_batch and not instance.material:
        instance.material = instance.material_batch.material


@receiver(post_save, sender=MaterialOut)
def post_save_material_out(sender, instance, **kwargs):
    current_amount = num_value(instance.amount, return_zero=True)
    current_record_datetime = instance.related_order.record_datetime
    storage_info = {
        'material_batch_id': instance.material_batch_id,
        'warehouse_id': instance.warehouse_id,
        'total_in': 0,
        'left_amount': 0,
        'cw_total_in': 0,
        'cw_left_amount': 0,
        'unit_price': 0,
        'unit_cost': 0,
        'record_datetime': current_record_datetime
    }
    # 获取该原料批次上次记录的库存
    pre_storage_cw = MaterialBatchStorage.objects.filter(
        material_batch_id=instance.material_batch_id,
        warehouse_id=instance.warehouse_id,
        record_datetime__lt=current_record_datetime
    ).order_by('-record_datetime').first()
    if pre_storage_cw:
        pre_cw_total_in = float(pre_storage_cw.cw_total_in)
        pre_cw_left_amount = float(pre_storage_cw.cw_left_amount)
        # 计算当前仓库剩余
        storage_info['cw_total_in'] = pre_cw_total_in
        storage_info['cw_left_amount'] = pre_cw_left_amount - current_amount
    else:
        storage_info['cw_total_in'] = 0
        storage_info['cw_left_amount'] = - current_amount
    pre_storage = MaterialBatchStorage.objects.filter(
        material_batch_id=instance.material_batch_id,
        record_datetime__lt=current_record_datetime
    ).order_by('-record_datetime').first()
    if pre_storage:
        pre_total_in = float(pre_storage.total_in)
        pre_left_amount = float(pre_storage.left_amount)
        pre_unit_price = float(pre_storage.unit_price)
        pre_unit_cost = float(pre_storage.unit_cost)
        # 计算当前剩余
        storage_info['total_in'] = pre_total_in
        storage_info['left_amount'] = pre_left_amount - current_amount
        storage_info['unit_price'] = pre_unit_price
        storage_info['unit_cost'] = pre_unit_cost
    else:
        storage_info['total_in'] = 0
        storage_info['left_amount'] = - current_amount
        storage_info['unit_price'] = 0
        storage_info['unit_cost'] = 0
    MaterialBatchStorage.objects.update_or_create(
        material_out_id=instance.pk,
        defaults=storage_info
    )
    update_material_batch(storage_info, instance.material_batch_id)
    # todo 改为异步任务处理
    update_next_record(instance.material_batch_id, current_record_datetime)


@receiver(pre_save, sender=MoneyInOut)
def pre_save_money_in_out(sender, instance, **kwargs):
    if instance.material_purchase_order and not instance.related_type:
        instance.related_type, created = MoneyInOutType.objects.get_or_create(name='原料入库', is_negative=True)
    if instance.related_type and instance.amount:
        if getattr(instance, 'related_type').is_negative:
            if float(getattr(instance, 'amount')) > 0:
                instance.amount = - float(getattr(instance, 'amount'))
        else:
            if float(getattr(instance, 'amount')) < 0:
                instance.amount = - float(getattr(instance, 'amount'))
    # 自动获取 operator
    set_operator(instance)


@receiver(post_save, sender=MoneyInOut)
def post_save_money_in_out(sender, instance, **kwargs):
    if instance.material_purchase_order:
        all_related_pays = MoneyInOut.objects.filter(
            material_purchase_order=instance.material_purchase_order
        ).aggregate(total_payed=Sum('amount'))
        total_payed = all_related_pays['total_payed']
        if total_payed:
            mpo = MaterialPurchaseOrder.objects.get(pk=instance.material_purchase_order.pk)
            if abs(total_payed) >= float(mpo.total_cost):
                mpo.state_pay = '3'
            else:
                mpo.state_pay = '2'
            mpo.total_payed = total_payed
            mpo.save()


@receiver(pre_save, sender=FermentMonitor)
def pre_save_ferment_monitor(sender, instance, **kwargs):
    if instance.sg_plato:
        if float(instance.sg_plato) < 1.19:
            instance.sg = instance.sg_plato
            instance.plato = sg2plato(float(instance.sg_plato))
        else:
            instance.plato = instance.sg_plato
            instance.sg = plato2sg(float(instance.sg_plato))
    # 自动获取 recorded_by
    set_operator(instance, field_name='recorded_by')


@receiver(pre_save, sender=GroupBatch)
def pre_save_group_batch(sender, instance, **kwargs):
    # 自动获取 operator
    set_operator(instance)


@receiver(post_save, sender=PackageItem)
def post_save_package_item(sender, instance, **kwargs):
    if instance.package_group:
        package_group = PackageGroup.objects.get(pk=instance.package_group.pk)
        if instance.package_group.calculate_weight_net:
            if not package_group.weight_net == package_group.calculate_weight_net():
                package_group.weight_net = package_group.calculate_weight_net()
                package_group.save()


@receiver(pre_save, sender=ProductName)
def pre_save_product_name(sender, instance, **kwargs):
    # 自动获取产品参数
    copy_fields = ['abv', 'plato', 'ibu', 'color', 'materials', 'standard']
    if instance.related_name:
        for f in copy_fields:
            setattr(instance, f, getattr(instance.related_name, f))


@receiver(post_save, sender=MaterialBatchStorage)
def post_save_material_batch_storage(sender, instance, **kwargs):
    # 更新原料的库存
    material = instance.material_batch.material
    mbs = MaterialBatch.objects.filter(material=material).values('total_in', 'left_amount')
    total_in = 0
    left_amount = 0
    for mb in mbs:
        total_in += num_value(mb['total_in'], return_zero=True)
        left_amount += num_value(mb['left_amount'], return_zero=True)
    Material.objects.filter(pk=material.pk).update(total_in=total_in, left_amount=left_amount)


@receiver(pre_save, sender=ProductPackage)
def pre_save_product_package(sender, instance, **kwargs):
    if instance.reference_cost:
        instance.has_cost = True
    else:
        instance.has_cost = False
    if instance.sales_cost:
        instance.has_sales_cost = True
    else:
        instance.has_sales_cost = False
